package com.springboot.fram.core.digest;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 抽象加密
 *
 * @author madman
 */
public class SimpleDigest {

  private static final int DEFAULT_HASH_ITERATIONS = 1;

  private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

  private MessageDigest digest;

  public SimpleDigest(String algorithmName) {
    try {
      digest = MessageDigest.getInstance(algorithmName);
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException(
          "No native '" + algorithmName + "' MessageDigest instance available on the current JVM.",
          e);
    }
  }

  /**
   * 计算16位MD5摘要值转为Base64编码
   *
   * @param data 被摘要数据
   * @return Base64编码的MD5摘要
   */
  public Encrypt encode(String data) {
    return encode(data, null, DEFAULT_HASH_ITERATIONS);
  }

  /**
   * 计算16位MD5摘要值转为Base64编码
   *
   * @param data 被摘要数据
   * @param salt 盐
   * @return Base64编码的MD5摘要
   */
  public Encrypt encode(String data, String salt) {
    return encode(data, salt, DEFAULT_HASH_ITERATIONS);
  }

  /**
   * 计算16位MD5摘要值转为Base64编码
   *
   * @param data 被摘要数据
   * @param salt 盐
   * @param hashIterations Hash次数
   * @return Base64编码的MD5摘要
   */
  public Encrypt encode(String data, String salt, int hashIterations) {
    byte[] dataBytes = data.getBytes(DEFAULT_CHARSET);
    byte[] saltBytes = salt != null ? salt.getBytes(DEFAULT_CHARSET) : null;
    return encode(dataBytes, saltBytes, hashIterations);
  }

  public Encrypt encode(byte[] data) {
    return encode(data, null, DEFAULT_HASH_ITERATIONS);
  }

  public Encrypt encode(byte[] data, byte[] salt) {
    return encode(data, salt, DEFAULT_HASH_ITERATIONS);
  }

  public Encrypt encode(byte[] data, byte[] salt, int hashIterations) {
    return new Encrypt(encrypt(data, salt, hashIterations));
  }

  public byte[] encrypt(byte[] data, byte[] salt, int hashIterations) {
    if (salt != null) {
      digest.reset();
      digest.update(salt);
    }
    byte[] hashed = digest.digest(data);
    for (int i = 0; i < hashIterations - 1; i++) {
      digest.reset();
      hashed = digest.digest(hashed);
    }
    return hashed;
  }

  public byte[] encode(InputStream in) throws IOException {
    try {
      byte[] buf = new byte[4096];
      int r;
      while ((r = in.read(buf)) != -1) {
        digest.update(buf, 0, r);
      }
    } finally {
      in.close();
    }
    return digest.digest();
  }

  public byte[] encode(File file) throws IOException {
    return encode(new FileInputStream(file));
  }

}
